package pl.wr.math.number;

import pl.wr.math.Component;

/**
 * Data type for complex number.
 * 
 * @version 1.0
 * @author wieslaw.rodak
 * 
 */
public class Complex extends Number implements Component{

	/**
	 * Field serialVersionUID.
	 * (value is -1634578340718966307)
	 */
	private static final long serialVersionUID = -1634578340718966307L;

	/**
	 * Real part
	 */
	private final double real;

	/**
	 * Imaginary part
	 */
	private final double imaginary;

	/**
	 * Constructs the complex number <code>z = a + b*i</code>
	 * 
	 * @param a
	 *            - Real part
	 * @param b
	 *            - Imaginary part
	 */
	public Complex(double a, double b) {
		real = a;
		imaginary = b;
	}

	/**
	
	 * @return <code>a</code> real part of this Complex number. */
	public double real() {
		return real;
	}

	/**
	
	 * @return <code>b</code> imaginary part of this Complex number */
	public double imaginary() {
		return imaginary;
	}

	/**
	
	 * @return <code>|z|</code> modulus of this Complex number. <code>Math.sqrt(a*a + b*b)</code> */
	public double modulus() {
		return Math.hypot(real, imaginary);
	}

	/**
	
	 * @return <code>arg(z)</code> argument of this Complex number. <code>Math.atan2(a, b)</code>, between -? and ? */
	public double argument() {
		return Math.atan2(imaginary, real);
	}

	/**
	 * Complex conjugate of this.
	 * 
	
	 * @return a new Complex number whose value is the conjugate of this (the conjugate of <code>a+b*i</code> is
	 *         <code>a-b*i</code>). */
	public Complex conjugate() {
		return new Complex(real, -imaginary);
	}

	/**
	 * Complex reciprocal of this.
	 * 
	
	 * @return a new Complex number whose value is the reciprocal of this */
	public Complex reciprocal() {
		final double scale = real * real + imaginary * imaginary;
		return new Complex(real / scale, -imaginary / scale);
	}

	/**
	 * Addition of Complex numbers.
	 * 
	 * @param c
	 *            is the number to add.
	
	 * @return a new Complex number <code>z+c</code> where <code>z</code> is this Complex number. */
	public Complex add(final Complex c) {
		return new Complex(real + c.real(), imaginary + c.imaginary());
	}

	/**
	 * Addition of Complex numbers.
	 * 
	 * @param a
	 *            - Complex number
	 * @param b
	 *            - Complex number
	
	 * @return a new Complex number <code>a + b</code> */
	public static Complex add(final Complex a, final Complex b) {
		final double real = a.real() + b.real();
		final double imaginary = a.imaginary() + b.imaginary();
		return new Complex(real, imaginary);
	}

	/**
	 * Subtraction of Complex numbers.
	 * 
	 * @param c
	 *            is the number to subtract.
	
	 * @return a new Complex number <code>z-c</code> where <code>z</code> is this Complex number. */
	public Complex substract(final Complex c) {
		return new Complex(real - c.real(), imaginary - c.imaginary());
	}

	/**
	 * Subtraction of Complex numbers.
	 * 
	 * @param a
	 *            - Complex number
	 * @param b
	 *            - Complex number
	
	 * @return a new Complex number <code>a-b</code> */
	public static Complex substract(final Complex a, final Complex b) {
		final double real = a.real() - b.real();
		final double imaginary = a.imaginary() - b.imaginary();
		return new Complex(real, imaginary);
	}

	/**
	 * Multiplication of Complex numbers.
	 * 
	 * @param c
	 *            is the number to multiply by.
	
	 * @return a new Complex number <code>z*c</code> where <code>z</code> is this Complex number. */
	public Complex multiply(final Complex c) {
		return new Complex(real * c.real() - imaginary * c.imaginary(), real * c.imaginary()
				+ imaginary * c.real());
	}

	/**
	 * Multiplication of Complex numbers.
	 * 
	 * @param a
	 *            - Complex number
	 * @param b
	 *            - Complex number
	
	 * @return a new Complex number <code>a*b</code> */
	public static Complex multiply(final Complex a, final Complex b) {
		return new Complex(a.real() * b.real() - a.imaginary() * b.imaginary(), a.real() * b.imaginary()
				+ a.imaginary() * b.real());

	}

	/**
	 * Complex multiplication.
	 * 
	 * @param alpha
	
	 * @return a new Complex number whose value is (this * alpha) */
	public Complex multiply(final double alpha) {
		return new Complex(alpha * real, alpha * imaginary);
	}

	/**
	 * Division of Complex numbers.
	 * 
	 * @param c
	 *            is the number to divide by
	
	 * @return new Complex number <code>z/c</code> where <code>z</code> is this Complex number */
	public Complex divide(final Complex c) {
		final double den = Math.pow(c.modulus(), 2);
		return new Complex((real * c.real() + imaginary * c.imaginary()) / den,
				(imaginary * c.real() - real * c.imaginary()) / den);
	}

	/**
	 * Division of Complex numbers.
	 * 
	 * @param a
	 *            - Complex number
	 * @param b
	 *            - Complex number
	
	 * @return a new Complex number <code>a/b</code> */
	public static Complex divide(final Complex a, final Complex b) {
		final double den = Math.pow(b.modulus(), 2);
		return new Complex((a.real() * b.real() + a.imaginary() * b.imaginary()) / den,
				(a.imaginary() * b.real() - a.real() * b.imaginary()) / den);
	}

	/**
	 * Complex exponential.
	 * 
	
	 * @return a new Complex number <code>exp(z)</code> where <code>z</code> is this Complex number. */
	public Complex exp() {
		return new Complex(Math.exp(real) * Math.cos(imaginary), Math.exp(real)
				* Math.sin(imaginary));
	}

	/**
	 * Logarithm of this Complex number.
	 * 
	
	 * @return a new Complex number <code>log(z)</code> where <code>z</code> is this Complex number. */
	public Complex log() {
		return new Complex(Math.log(this.modulus()), this.argument());
	}

	/**
	 * Complex square root.
	 * 
	
	 * @return a new Complex number <code>sqrt(z)</code> where <code>z</code> is this Complex number. */
	public Complex sqrt() {
		final double msqr = Math.sqrt(this.modulus());
		final double theta = this.argument() / 2;
		return new Complex(msqr * Math.cos(theta), msqr * Math.sin(theta));
	}

	/**
	 * Sine of this Complex number.
	 * 
	
	 * @return a new Complex number <code>sin(z)</code> where <code>z</code> is this Complex number. */
	public Complex sin() {
		return new Complex(Math.sin(real) * Math.cosh(imaginary), Math.cos(real)
				* Math.sinh(imaginary));
	}

	/**
	 * Cosine of this Complex number.
	 * 
	
	 * @return a new Complex number <code>cos(z)</code> where <code>z</code> is this Complex number. */
	public Complex cos() {
		return new Complex(Math.cos(real) * Math.cosh(imaginary), -Math.sin(real)
				* Math.sinh(imaginary));
	}

	/**
	 * Hyperbolic sine of this Complex number.
	 * 
	
	 * @return a new Complex number <code>sinh(z)</code> where <code>z</code> is this Complex number. */
	public Complex sinh() {
		return new Complex(Math.sinh(real) * Math.cos(imaginary), Math.cosh(real)
				* Math.sin(imaginary));
	}

	/**
	 * Hyperbolic cosine of this Complex number).
	 * 
	
	 * @return a new Complex number <code>cosh(z)</code> where <code>z</code> is this Complex number. */
	public Complex cosh() {
		return new Complex(Math.cosh(real) * Math.cos(imaginary), Math.sinh(real)
				* Math.sin(imaginary));
	}

	/**
	 * Tangent of this Complex number.
	 * 
	
	 * @return <code>tan(z)</code> where <code>z</code> is this Complex number. */
	public Complex tan() {
		return (this.sin()).divide(this.cos());
	}

	/**
	 * Negative of this complex number.
	 * 
	
	 * @return a new Complex number <code>-z</code> where <code>z</code> is this Complex number. */
	public Complex negative() {
		return new Complex(-real, -imaginary);
	}

	/**
	 * Method toString.
	 * @return String
	 */
	@Override
	public String toString() {

		if (this.imaginary == 0) {
			return String.valueOf(this.real);
		} else if (this.real == 0) {
			return this.imaginary + "i";
		} else if (this.imaginary > 0) {
			return this.real + " + " + this.imaginary + "i";
		} else {
			return this.real + " - " + (-this.imaginary) + "i";
		}
	}

	/**
	 * Method intValue.
	 * @return int
	 */
	@Override
	public int intValue() {
		return (int) modulus();
	}

	/**
	 * Method longValue.
	 * @return long
	 */
	@Override
	public long longValue() {
		return (long) modulus();
	}

	/**
	 * Method floatValue.
	 * @return float
	 */
	@Override
	public float floatValue() {
		return (float) modulus();
	}

	/**
	 * Method doubleValue.
	 * @return double
	 */
	@Override
	public double doubleValue() {
		return modulus();
	}
	
}
